
#include "StdAfx.h"
#include "AnimationRandomizer.h"

CAnimationRandomizer::CAnimationRandomizer() 
: m_animIndex(INVALID_INDEX)
, m_probabilitySum(0.0f)
, m_elapsedTimeFromEnterState(0.0f)
, m_initialDelayApplied(false)
{
}

CAnimationRandomizer::CAnimationRandomizer(const CAnimationRandomizer& other)
: m_animIndex(other.m_animIndex)
, m_probabilitySum(other.m_probabilitySum)
, m_elapsedTimeFromEnterState(other.m_elapsedTimeFromEnterState)
, m_initialDelayApplied(other.m_initialDelayApplied)
, m_generalParams(other.m_generalParams)
, m_animations(other.m_animations)
{
}

bool CAnimationRandomizer::IsItTimeForAnimationSwitch(float animTime /*= -1.0f*/)
{
	if (!m_generalParams.switchAssetsOverTime && !HasInitialDelay())
		return false;

	if (HasInitialDelay() && !m_initialDelayApplied)
	{
		m_initialDelayApplied = m_generalParams.initialDelay < m_elapsedTimeFromEnterState;
		return m_initialDelayApplied;
	}

	CRY_ASSERT_MESSAGE(m_animIndex != INVALID_INDEX, "CAnimationRandomizer::IsItTimeForAnimationSwitch() has no valid animation selected!");

	// can the asset currently running be interrupted
	// and if not, is it already done so we can start the next one?
	// irrelevant for looping animations of course
	if (!m_animations[m_animIndex].looping && !m_animations[m_animIndex].allowInterrupt)
	{
		if (animTime >= 0.0f && animTime < 0.98f)
			return false;
	}

	// Explanation:
	// Switch randomly once every x seconds (x = m_switchProbabilitySeconds)
	// Probability this frame = (frameTime / x)

	// Is it time for an asset switch?
	if (m_generalParams.switchAssetsOverTime)
	{
		// can the asset currently running be interrupted
		// and if not, is it already done so we can start the next one?
		// irrelevant for looping animations of course
		if (!m_animations[m_animIndex].looping && !m_animations[m_animIndex].allowInterrupt)
		{
			if (animTime >= 0.0f && animTime < 0.98f)
				return false;
		}

		if (m_generalParams.switchProbabilitySeconds <= 0.0f)
			return true;

		// if the animation is not looping, it is finished (almost) and the flag is set to switch immediately to the next asset in this case
		if (!m_animations[m_animIndex].looping && m_generalParams.switchOnNonLoopFinish && animTime > 0.99f)
			return true;

		// this is the actual random per time calculation, which is only reached if none of the above
		// already handled this
		float randomNumber = (cry_rand() / (float)RAND_MAX);
		float frameTime = gEnv->pTimer->GetFrameTime();
		if (randomNumber < (frameTime / m_generalParams.switchProbabilitySeconds))
		{
			return true;
		}
	}

	return false;
}

void CAnimationRandomizer::SetAnimationParams( CryCharAnimationParams& params ) const
{
	if (m_animIndex != INVALID_INDEX)
	{
		// time warping
		if (m_generalParams.switchAssetsOverTime)
		{
			if (m_generalParams.timeAlignBetweenAssets)
				params.m_nFlags |= CA_TRANSITION_TIMEWARPING;
			else
				params.m_nFlags &= ~CA_TRANSITION_TIMEWARPING;
		}

		const CRandomAnim& anim = m_animations[m_animIndex];

		// looping
		if (anim.looping)
			params.m_nFlags |= CA_LOOP_ANIMATION;
		else
		{
			params.m_nFlags &= ~CA_LOOP_ANIMATION;
			if (m_generalParams.switchAssetsOverTime && m_generalParams.switchOnNonLoopFinish)
				params.m_nFlags |= CA_REPEAT_LAST_KEY;
		}

		// transition time
		params.m_fTransTime = anim.transitionTime;
	}
}

void CAnimationRandomizer::EnterState()
{
	// If no animation has been selected yet
	if (m_animIndex == INVALID_INDEX)
		ChooseNextAnimation();

	m_elapsedTimeFromEnterState = 0.0f;
	m_initialDelayApplied = false;
}

void CAnimationRandomizer::LeaveState()
{
	// Move to the next animation - it stays 'chosen' to allow state queries to be safe at any time.
	// This is critically important for exact positioning or other into-the-future style queries.
	ChooseNextAnimation();
}

void CAnimationRandomizer::Update()
{
	m_elapsedTimeFromEnterState += gEnv->pTimer->GetFrameTime();
}

bool CAnimationRandomizer::HasInitialDelay() const
{
	return m_generalParams.initialDelay > 0.0f;
}

CCryName CAnimationRandomizer::ChooseNextAnimation()
{
	const size_t animCount = m_animations.size();
	CRY_ASSERT(animCount);

	if (animCount == 1)
	{
		m_animIndex = 0;
		return m_animations.front().animName;
	}

	size_t nextAnimIndex = INVALID_INDEX;
	bool valid;
	do 
	{
		valid = true;
		float randomNumber = (cry_rand() / (float)RAND_MAX) * m_probabilitySum;

		for (size_t i = 0; i < animCount; ++i)	
		{
			float assetProb = static_cast<float>(m_animations[i].probability);
			if (randomNumber <= assetProb)
			{
				// maybe this animation was already selected as the last one and should not be started twice in a row
				if (m_animations[i].canBeStartedTwiceInARow == false && m_animIndex == i)
				{
					valid = false;
					break;
				}

				nextAnimIndex = i;
				break;
			}

			randomNumber -= assetProb;
		}
	} while (!valid);

	CRY_ASSERT(nextAnimIndex < m_animations.size());
	if (nextAnimIndex < m_animations.size())
	{
		m_animIndex = nextAnimIndex;
		return m_animations[nextAnimIndex].animName;
	}
	else
	{
		CryLog("CAnimationRandomizer::GetNextAssetName() invalid nextAnimIndex {%u}", (uint32)nextAnimIndex);
		m_animIndex = INVALID_INDEX;
		return CCryName();
	}
}

bool CAnimationRandomizer::Init( XmlNodeRef node, IAnimationGraphPtr pGraph )
{
	// Safety checking
	if (strcmp(node->getTag(), "RandomizerSet") != 0)
	{
		CryWarning(VALIDATOR_MODULE_ANIMATION, VALIDATOR_ERROR, "AnimationGraph: XML node is not an AnimationRadnomizer node. Cannot load data. Aborting.");
		return false;
	}

	if (!m_animations.empty())
	{
		m_animations.clear();
	}

	// go through all children
	int childCount = node->getChildCount();
	CRandomAnim newAnim;
	for (int i = 0; i < childCount; ++i)
	{
		XmlNodeRef child = node->getChild(i);

		// Could be a parameter's node
		if (m_generalParams.LoadFromXml(child))
			continue;

		// Try to load a random Animation Asset
		if (newAnim.LoadFromXml(child))
		{
			newAnim.animName = pGraph->RegisterVariationInputs( newAnim.animName.c_str() );
			m_animations.push_back(newAnim);
		}
	}

	// final step after loading all animations
	CalculateProbabilitySum();

	return true;
}


void CAnimationRandomizer::SerializeAsFile( bool reading, AG_FILE *file )
{
	FileSerializationHelper h(reading, file);

	m_generalParams.SerializeAsFile(reading, file);

	int animCount = m_animations.size();
	h.Value(&animCount);
	m_animations.resize(animCount);

	for (int i = 0; i < animCount; ++i)
	{
		m_animations[i].SerializeAsFile(reading, file);
	}

	CalculateProbabilitySum();
}

void CAnimationRandomizer::CalculateProbabilitySum()
{
	m_probabilitySum = 0;
	size_t animCount = m_animations.size();
	for (size_t i = 0; i < animCount; ++i)
		m_probabilitySum += m_animations[i].probability;
}

//////////////////////////////////////////////////////////////////////////


void CRandomGeneralParams::SerializeAsFile( bool reading, AG_FILE *file )
{
	FileSerializationHelper h(reading, file);

	h.Value(&switchAssetsOverTime);
	h.Value(&timeAlignBetweenAssets);
	h.Value(&switchOnNonLoopFinish);
	h.Value(&switchProbabilitySeconds);
	h.Value(&initialDelay);
}

void CRandomAnim::SerializeAsFile( bool reading, AG_FILE *file )
{
	FileSerializationHelper h(reading, file);

	h.Value(&canBeStartedTwiceInARow);
	h.Value(&looping);
	h.Value(&allowInterrupt);
	h.Value(&probability);
	h.Value(&transitionTime);
	h.Value(&animName);
}
